%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     网络通讯连接程序
%%% @end
%%%-------------------------------------------------------------------
-module(net_tcp_conn).

-behaviour(gen_server).

-export([start/3]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-include("common.hrl").
-include("net.hrl").

-define(time_heartbeat, 30).
-define(timer_heartbeat, erlang:send_after(timer:seconds(?time_heartbeat), erlang:self(), timer_heartbeat)).
-define(time_read_data, 40000).


%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

start(Module, ClientType, Socket) ->
    gen_server:start(?MODULE, [Module, ClientType, Socket], []).


init([Module, ClientType, Socket]) ->
    erlang:process_flag(trap_exit, true),

    erlang:self() ! read_next,
    ?timer_heartbeat,

    {ok, {Ip, Port}} = inet:peername(Socket),
    State = #net_conn{module = Module, type = ClientType, socket = Socket, ip = util_type:ip_to_bitstring(Ip), port = Port},
    {ok, State}.

handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(_Request, State) ->
    {noreply, State}.


handle_info(timer_heartbeat, State = #net_conn{account = Account, hb_last = HbLast}) ->
    Now = util_time:unixtime(),
    Diff = Now - HbLast,
    case Diff > ?time_heartbeat of
        true ->
            ?ERR("帐号[~s]的客户端心跳包超时, now=~w,HbLast=~w,diff=~w", [Account, Now, HbLast, Diff]),
            {stop, normal, State};
        false ->
            ?timer_heartbeat,
            {noreply, State}
    end;

handle_info(read_next, State) ->
    read_next(State);

handle_info({send, Bin}, State = #net_conn{account = Account, socket = Socket, send_count = SendCount, error_send = ErrSend}) ->
    case gen_tcp:send(Socket, Bin) of
        ok ->
            {noreply, State#net_conn{send_count = SendCount + 1}};
        {error, Reason} ->
            ?ERR("帐号[~s]发送数据失败:~w", [Account, Reason]),
            {noreply, State#net_conn{error_send = ErrSend + 1}}
    end;

handle_info({send_filter, Bin}, State = #net_conn{account = Account, object_id = Oid, socket = Socket, send_count = SendCount, error_send = ErrSend}) ->
    case filter_self(Bin, [], Oid) of
        [] -> {noreply, State};
        Bin2 ->
            case gen_tcp:send(Socket, Bin2) of
                ok ->
                    {noreply, State#net_conn{send_count = SendCount + 1}};
                {error, Reason} ->
                    ?ERR("帐号[~s]发送数据失败:~w", [Account, Reason]),
                    {noreply, State#net_conn{error_send = ErrSend + 1}}
            end
    end;

%%%===================================================================
%%% 处理 socket 读取结果
%%%===================================================================

%% 客户端断开连接
handle_info({inet_async, _Socket, _Ref, {error, closed}}, State) ->
    ?DEBUG("客户端断开连接"),
    {stop, normal, State};

%% 收到包头数据
handle_info({inet_async, Socket, _Ref, {ok, Bin}}, State = #net_conn{read_head = true, module = Module}) ->
    case Module:on_head(Bin) of
        {ok, 0} ->
            do_packet(Bin, <<>>, State#net_conn{read_head = false});
        {ok, BodyLen} ->
            prim_inet:async_recv(Socket, BodyLen, 10000),
            {noreply, State#net_conn{read_head = false, read_cont = true, head_bin = Bin}};
        _ ->
            {stop, normal, State}
    end;

%% 收到包体数据
handle_info({inet_async, _Socket, _Ref, {ok, Bin}}, State = #net_conn{read_cont = true, head_bin = HeadBin}) ->
    do_packet(HeadBin, Bin, State#net_conn{read_cont = false});

%% 收到异常数据
handle_info({inet_async, Socket, _Ref, {ok, Bin}}, State = #net_conn{account = Account, ip = Ip, socket = Socket, bad_req_count = BadReq, read_head = ReadHead, read_cont = ReadCont}) ->
    ?ERR("客户端[Acc:~s IP:~p]发送了无效请求: ~w,read_head=~w,read_cont:~w,bad_req=~w", [Account, Ip, Bin, ReadHead, ReadCont, BadReq]),
    % 最多记录10个错误，不要全部记录，避免被攻击
    case BadReq >= 10 of
        false ->
            {noreply, State#net_conn{bad_req_count = BadReq + 1}};
        true ->
            {stop, normal, State}
    end;
%% 接收socket数据时发生了未预料的错误
handle_info({inet_async, _Socket, _Ref, {error, _Reason}}, State = #net_conn{account = Account}) ->
    ?ERR("帐号[~s]读取socket数据出错:~w", [Account, _Reason]),
    {stop, normal, State};

%%%===================================================================
%%% 处理 socket 发送结果
%%%===================================================================

handle_info({inet_reply, _Socket, ok}, State) ->
    {noreply, State};
handle_info({inet_reply, _Socket, {error, closed}}, State) ->
    ?ERR("发送消息时, {error, closed}"),
    {stop, normal, State};
handle_info({inet_reply, _Socket, {error, timeout}}, State = #net_conn{error_send = ErrSend}) ->
    ?ERR("发送消息时, {error, timeout}"),
    {noreply, State#net_conn{error_send = ErrSend + 1}};
handle_info({inet_reply, _Socket, _Else}, State = #net_conn{account = _Account}) ->
    ?ERR("帐号[~s]发送socket数据时发生了未预料的错误: ~w", [_Account, _Else]),
    {stop, normal, State};


handle_info({'EXIT', _Pid, normal}, State) ->
    {stop, normal, State};

%% 处理关联进程异常退出
handle_info({'EXIT', OPid, Why}, State = #net_conn{module = Module, account = Account, object_pid = OPid, object_id = OId}) ->
    ?ERR("帐号[~s]控制的目标进程[~w]异常退出:~w", [Account, OPid, Why]),
    Module:on_exit(OId),
    {stop, normal, State};

handle_info(_Info, State) ->
    ?ERR("收到未知数据~n~p", [_Info]),
    {noreply, State}.


terminate(_Reason, #net_conn{module = Module, account = _Account, socket = Socket, object = Object, object_id = OPid}) ->
    catch Module:on_close(Object, OPid),
    gen_tcp:close(Socket),
    ?DEBUG("帐号[~s]的连接进程已退出", [_Account]),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%%===================================================================
%%% Internal functions
%%%===================================================================

read_next(State = #net_conn{module = Module, socket = Socket, recv_count = RecvCount, read_head = false, read_cont = false}) ->
    prim_inet:async_recv(Socket, Module:head_size(), ?time_read_data),
    {noreply, State#net_conn{recv_count = RecvCount + 1, read_head = true}};
read_next(State) ->
    {noreply, State}.

do_packet(HeadBin, BodyBin, State = #net_conn{module = Module}) ->
    case Module:on_packet(HeadBin, BodyBin, State) of
        {ok, StateNew} ->
            {noreply, StateNew};
        {next, StateNew} ->
            read_next(StateNew);
        stop ->
            {stop, normal, State}
    end.

filter_self([], Acc, _Oid) ->
    Acc;
filter_self([H | T], Acc, Oid) when erlang:is_list(H) ->
    filter_self(T, filter_self(H, Acc, Oid), Oid);
filter_self([{Oid, _} | T], Acc, Oid) ->
    filter_self(T, Acc, Oid);
filter_self([{_, Bin} | T], Acc, Oid) ->
    filter_self(T, [Bin | Acc], Oid);
filter_self([Bin | T], Acc, Oid) ->
    filter_self(T, [Bin | Acc], Oid).
